# status.py - working copy browser
#
# Copyright 2010 Steve Borho <steve@borho.org>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

import os

from mercurial import ui, hg, util, patch, cmdutil, error, mdiff
from mercurial import context, merge, commands, subrepo
from tortoisehg.hgqt import qtlib, htmlui, chunkselect, wctxactions, visdiff
from tortoisehg.util import paths, hglib, thgrepo
from tortoisehg.util.util import xml_escape
from tortoisehg.hgqt.i18n import _

from PyQt4.QtCore import *
from PyQt4.QtGui import *

# This widget can be used as the basis of the commit tool or any other
# working copy browser.

# Technical Debt
#  We need a real icon set for file status types
#  Thread rowSelected, connect to an external progress bar
#  Chunk selection, tri-state checkboxes for commit
# Maybe, Maybe Not
#  Investigate folding/nesting of files
#  Toolbar
#  double-click visual diffs

COL_PATH = 0
COL_STATUS = 1
COL_MERGE_STATE = 2
COL_PATH_DISPLAY = 3
COL_EXTENSION = 4
COL_SIZE = 5

_colors = {}

class StatusWidget(QWidget):
    '''Working copy status widget
       SIGNALS:
       loadBegin()                  - for progress bar
       loadComplete()               - for progress bar
       errorMessage(QString)        - for status bar
       titleTextChanged(QString)    - for window title
    '''
    loadBegin = pyqtSignal()
    loadComplete = pyqtSignal()
    def __init__(self, pats, opts, root=None, parent=None):
        QWidget.__init__(self, parent)

        root = paths.find_root(root)
        assert(root)
        self.repo = thgrepo.repository(ui.ui(), path=root)
        self.wctx = self.repo[None]
        self.opts = dict(modified=True, added=True, removed=True, deleted=True,
                         unknown=True, clean=False, ignored=False, subrepo=True)
        self.opts.update(opts)
        self.pats = pats
        self.ms = {}
        self.curRow = None
        self.patchecked = {}
        self.refreshing = None

        # determine the user configured status colors
        # (in the future, we could support full rich-text tags)
        qtlib.configstyles(self.repo.ui)
        labels = [(stat, val.uilabel) for stat, val in statusTypes.items()]
        labels.extend([('r', 'resolve.resolved'), ('u', 'resolve.unresolved')])
        for stat, label in labels:
            effect = qtlib.geteffect(label)
            for e in effect.split(';'):
                if e.startswith('color:'):
                    _colors[stat] = QColor(e[7:])
                    break

        SP = QSizePolicy

        split = QSplitter(Qt.Horizontal)
        split.setChildrenCollapsible(False)
        layout = QVBoxLayout()
        layout.setMargin(0)
        layout.addWidget(split)
        self.setLayout(layout)

        vbox = QVBoxLayout()
        vbox.setMargin(0)
        frame = QFrame(split)
        sp = SP(SP.Expanding, SP.Expanding)
        sp.setHorizontalStretch(0)
        sp.setVerticalStretch(0)
        frame.setSizePolicy(sp)
        frame.setLayout(vbox)
        hbox = QHBoxLayout()
        hbox.setContentsMargins (5, 0, 0, 0)
        lbl = QLabel(_('Filter:'))
        le = QLineEdit()
        pb = QPushButton(_('MAR!?IC')) # needs a better label
        hbox.addWidget(lbl)
        hbox.addWidget(le)
        hbox.addWidget(pb)
        tv = WctxFileTree(self.repo)
        vbox.addLayout(hbox)
        vbox.addWidget(tv)
        split.addWidget(frame)

        if self.pats:
            def clearPattern():
                self.pats = []
                self.refreshWctx()
                cpb.setVisible(False)
                self.emit(SIGNAL('titleTextChanged'), QString(self.getTitle()))
            cpb = QPushButton(_('Remove filter, show root'))
            vbox.addWidget(cpb)
            cpb.clicked.connect(clearPattern)

        self.countlbl = QLabel()
        hcbox = QHBoxLayout()
        vbox.addLayout(hcbox)
        hcbox.addSpacing(6)
        hcbox.addWidget(self.countlbl)

        self.connect(tv, SIGNAL('clicked(QModelIndex)'), self.rowSelected)
        self.connect(tv, SIGNAL('menuAction()'), self.refreshWctx)
        tv.setItemsExpandable(False)
        tv.setRootIsDecorated(False)
        tv.sortByColumn(COL_PATH_DISPLAY)

        def setButtonText():
            text = ''
            for stat in StatusType.preferredOrder:
                name = statusTypes[stat].name
                if self.opts[name]:
                    text += stat
            pb.setText(text)
        def statusTypeTrigger(isChecked):
            txt = hglib.fromunicode(self.sender().text())
            self.opts[txt[2:]] = isChecked
            self.refreshWctx()
            setButtonText()
        menu = QMenu()
        for stat in StatusType.preferredOrder:
            val = statusTypes[stat]
            a = menu.addAction('%s %s' % (stat, val.name))
            a.setCheckable(True)
            a.setChecked(self.opts[val.name])
            a.triggered.connect(statusTypeTrigger)
        pb.setMenu(menu)
        setButtonText()
        pb.storeref = menu
        self.tv = tv
        self.le = le

        # Diff panel side of splitter
        vbox = QVBoxLayout()
        vbox.setSpacing(0)
        vbox.setContentsMargins(0, 0, 0, 0)
        docf = QFrame(split)
        sp = SP(SP.Expanding, SP.Expanding)
        sp.setHorizontalStretch(1)
        sp.setVerticalStretch(0)
        docf.setSizePolicy(sp)
        docf.setLayout(vbox)
        self.docf = docf
        hbox = QHBoxLayout()
        hbox.setContentsMargins (0, 0, 0, 0)
        self.fnamelabel = QLabel()
        self.fnamelabel.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self.fnamelabel,
                     SIGNAL('customContextMenuRequested(const QPoint &)'),
                     self.customContextMenuRequested)
        hbox.addWidget(self.fnamelabel)
        hbox.addStretch()

        hbox.addSpacing(6)

        self.te = QTextBrowser()
        self.te.setOpenLinks(False)
        self.connect(self.te, SIGNAL('anchorClicked(QUrl)'), self.teLinkClicked)
        self.te.document().setDefaultStyleSheet(qtlib.thgstylesheet)
        self.te.setReadOnly(True)
        self.te.setLineWrapMode(QTextEdit.NoWrap)
        vbox.addLayout(hbox)
        vbox.addWidget(self.te, 1)

        self.split = split
        self.diffvbox = vbox
        self.override = False
        QTimer.singleShot(0, self.refreshWctx)

    def teLinkClicked(self, url):
        self.override = True
        self.refreshDiff()

    def getTitle(self):
        if self.pats:
            return hglib.tounicode(_('%s - status (selection filtered)') %
                                   hglib.get_reponame(self.repo))
        else:
            return hglib.tounicode(_('%s - status') %
                                   hglib.get_reponame(self.repo))

    def restoreState(self, data):
        return self.split.restoreState(data)

    def saveState(self):
        return self.split.saveState()

    def customContextMenuRequested(self, point):
        'menu request for filename label'
        if self.curRow is None:
            return
        point = self.fnamelabel.mapToGlobal(point)
        path, status, mst, u = self.curRow
        selrows = [(set(status+mst.lower()), path), ]
        action = wctxactions.wctxactions(self, point, self.repo, selrows)
        if action:
            self.emit(SIGNAL('menuAction()'))

    def keyPressEvent(self, event):
        if event.matches(QKeySequence.Refresh):
            self.te.clear()
            self.refreshWctx()
        else:
            return super(StatusWidget, self).keyPressEvent(event)

    def refreshWctx(self):
        if self.refreshing:
            return
        self.te.clear()
        self.fnamelabel.clear()
        self.curRow = None
        self.override = False

        # store selected paths or current path
        model = self.tv.model()
        if model:
            sp = [model.getRow(i)[COL_PATH] for i in self.tv.selectedRows()]
            if not sp:
                index = self.tv.selectionModel().currentIndex()
                if index.isValid():
                    sp = [model.getRow(index)[COL_PATH]]
        else:
            sp = None
        self.sp = sp

        self.loadBegin.emit()
        self.refreshing = StatusThread(self.repo, self.pats, self.opts)
        self.connect(self.refreshing, SIGNAL('finished'), self.reloadComplete)
        # re-emit error messages from this object
        self.connect(self.refreshing, SIGNAL('errorMessage'),
                     lambda msg: self.emit(SIGNAL('errorMessage'), msg))
        self.refreshing.start()

    def reloadComplete(self, wctx, patchecked):
        self.ms = merge.mergestate(self.repo)
        self.wctx = wctx
        self.patchecked = patchecked.copy()
        self.updateModel()
        self.loadComplete.emit()
        self.refreshing.wait()
        self.refreshing = None

    def updateModel(self):
        self.tv.setSortingEnabled(False)
        if self.tv.model():
            checked = self.tv.model().getChecked()
        else:
            checked = self.patchecked
            if self.pats and not self.patchecked:
                qtlib.WarningMsgBox(_('No appropriate files'),
                                    _('No files found for this operation'),
                                    parent=self)
        tm = WctxModel(self.wctx, self.ms, self.opts, checked)
        self.tv.setModel(tm)
        self.tv.setSortingEnabled(True)
        self.tv.setColumnHidden(COL_PATH, self.isMerge())
        self.tv.setColumnHidden(COL_MERGE_STATE, not tm.anyMerge())
        for col in xrange(COL_SIZE):
            self.tv.resizeColumnToContents(col)
        self.connect(self.tv, SIGNAL('activated(QModelIndex)'), tm.toggleRow)
        self.connect(self.tv, SIGNAL('pressed(QModelIndex)'), tm.pressedRow)
        self.connect(self.le, SIGNAL('textEdited(QString)'), tm.setFilter)
        self.connect(tm, SIGNAL('checkToggled()'), self.updateCheckCount)
        self.updateCheckCount()

        # reset selection, or select first row
        selmodel = self.tv.selectionModel()
        flags = QItemSelectionModel.Select | QItemSelectionModel.Rows
        if self.sp:
            first = None
            for i, row in enumerate(tm.getAllRows()):
                if row[COL_PATH] in self.sp:
                    index = tm.index(i, 0)
                    selmodel.select(index, flags)
                    if not first: first = index
        else:
            first = tm.index(0, 0)
            selmodel.select(first, flags)
        if first and first.isValid():
            self.rowSelected(first)

    def updateCheckCount(self):
        text = _('Checkmarked file count: %d') % len(self.getChecked())
        self.countlbl.setText(text)

    def isMerge(self):
        return bool(self.wctx.p2())

    def getChecked(self, types=None):
        model = self.tv.model()
        if model:
            checked = model.getChecked()
            if types is None:
                return [f for f, v in checked.iteritems() if v]
            else:
                files = []
                for row in model.getAllRows():
                    path, status, mst, upath, ext, sz = row
                    if status in types and checked[path]:
                        files.append(path)
                return files
        else:
            return []

    def rowSelected(self, index):
        'Connected to treeview "clicked" signal'
        self.curRow = None
        self.override = False
        self.curRow = index.model().getRow(index)
        self.refreshDiff()

    def refreshDiff(self):
        if self.curRow is None:
            return
        path, status, mst, upath, ext, sz = self.curRow
        showanyway = self.override
        wfile = util.pconvert(path)
        self.fnamelabel.setText(statusMessage(status, mst, upath))
        hu = htmlui.htmlui()

        show = '&nbsp;&nbsp;(<a href="cmd:show">%s</a>)' % _('show anyway')
        if status in '?IA':
            if showanyway:
                # Read untracked file contents from working directory
                try:
                    diff = open(self.repo.wjoin(wfile), 'r').read()
                except EnvironmentError, e:
                    diff = '<b>%s</b>' % str(e)
                if '\0' in diff:
                    diff = _('<b>Contents are binary, not previewable</b>')
                    self.te.setHtml(diff)
                else:
                    diff = '<pre>%s</pre>' % xml_escape(diff)
                    self.te.setHtml(diff)
            else:
                diff = _('<b>Not displayed</b>') + show
                self.te.setHtml(diff)
            return
        elif status in '!C':
            if showanyway:
                # Read file contents from parent revision
                ctx = self.repo['.']
                diff = ctx.filectx(wfile).data()
                if '\0' in diff:
                    diff = _('<b>Contents are binary, not previewable</b>')
                    self.te.setHtml(diff)
                else:
                    diff = '<pre>%s</pre>' % xml_escape(diff)
                    self.te.setHtml(diff)
            else:
                diff = _('<b>Not displayed</b>') + show
                self.te.setHtml(diff)
            return
        elif status in 'S':
            if showanyway:
                try:
                    sroot = self.repo.wjoin(path)
                    srepo = thgrepo.repository(hu, path=sroot)
                    srev = self.wctx.substate.get(path, subrepo.nullstate)[1]
                    sactual = srepo['.'].hex()
                    commands.status(hu, srepo)
                    out = [hu.getdata()[0]]
                    if srev != sactual:
                        out.append('<b>')
                        out.append(_('revision changed from:'))
                        out.append('</b><br>')
                        opts = {'date':None, 'user':None, 'rev':[srev]}
                        commands.log(hu, srepo, **opts)
                        out.append(hu.getdata()[0])
                        out.append('<b>')
                        out.append(_('to:'))
                        out.append('</b><br>')
                        opts['rev'] = [sactual]
                        commands.log(hu, srepo, **opts)
                        out.append(hu.getdata()[0])
                        diff = ''.join(out)
                except error.RepoError:
                    diff = _('<b>Not an hg subrepo, not previewable</b>')
            else:
                diff = _('<b>Subrepository status not displayed</b>') + show
            self.te.setHtml(diff)
            return

        warnings = chunkselect.check_max_diff(self.wctx, wfile)
        if warnings and not showanyway:
            text = '<b>Diffs not displayed: %s</b>' % warnings[1] + show
            self.te.setHtml(text)
            return

        # Generate diffs to first parent
        m = cmdutil.matchfiles(self.repo, [wfile])
        try:
            for s, l in patch.difflabel(self.wctx.diff, match=m, git=True):
                hu.write(s, label=l)
        except (IOError, error.RepoError, error.LookupError, util.Abort), e:
            err = hglib.tounicode(str(e))
            self.emit(SIGNAL('errorMessage'), QString(err))
            return
        o, e = hu.getdata()
        diff = o or _('<em>No displayable differences</em>')

        if self.isMerge():
            header = _('===== Diff to first parent %d:%s =====\n') % (
                    self.wctx.p1().rev(), str(self.wctx.p1()))
            header = '<h3>' + header + '</h3></br>'
            text = header + diff
        else:
            self.te.setHtml(diff)
            return

        # Generate diffs to second parent
        try:
            for s, l in patch.difflabel(self.wctx.diff, self.wctx.p2(),
                                        match=m, git=True):
                hu.write(s, label=l)
        except (IOError, error.RepoError, error.LookupError, util.Abort), e:
            err = hglib.tounicode(str(e))
            self.emit(SIGNAL('errorMessage'), QString(err))
            return
        text += '</br><h3>'
        text += _('===== Diff to second parent %d:%s =====\n') % (
                self.wctx.p2().rev(), str(self.wctx.p2()))
        text += '</h3></br>'
        o, e = hu.getdata()
        diff = o or _('<em>No displayable differences</em>')
        self.te.setHtml(text + diff)


class StatusThread(QThread):
    '''Background thread for generating a workingctx'''
    def __init__(self, repo, pats, opts, parent=None):
        super(StatusThread, self).__init__()
        self.repo = repo
        self.pats = pats
        self.opts = opts

    def run(self):
        self.repo.thginvalidate()
        extract = lambda x, y: dict(zip(x, map(y.get, x)))
        stopts = extract(('unknown', 'ignored', 'clean'), self.opts)
        patchecked = {}
        try:
            if self.pats:
                m = cmdutil.match(self.repo, self.pats)
                status = self.repo.status(match=m, **stopts)
                # Record all matched files as initially checked
                for i, stat in enumerate(StatusType.preferredOrder):
                    if stat == 'S':
                        continue
                    val = statusTypes[stat]
                    if self.opts[val.name]:
                        d = dict([(fn, True) for fn in status[i]])
                        patchecked.update(d)
                wctx = context.workingctx(self.repo, changes=status)
            else:
                wctx = self.repo[None]
                wctx.status(**stopts)
        except (OSError, IOError, util.Abort), e:
            err = hglib.tounicode(str(e))
            self.emit(SIGNAL('errorMessage'), QString(err))
        try:
            wctx.dirtySubrepos = []
            for s in wctx.substate:
                if wctx.sub(s).dirty():
                    wctx.dirtySubrepos.append(s)
        except (OSError, IOError, util.Abort,
                error.RepoLookupError, error.ConfigError), e:
            err = hglib.tounicode(str(e))
            self.emit(SIGNAL('errorMessage'), QString(err))
        self.emit(SIGNAL('finished'), wctx, patchecked)


class WctxFileTree(QTreeView):
    def __init__(self, repo, parent=None):
        QTreeView.__init__(self, parent)
        self.repo = repo
        self.setSelectionMode(QTreeView.ExtendedSelection)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.connect(self, SIGNAL('customContextMenuRequested(const QPoint &)'),
                     self.customContextMenuRequested)

    def keyPressEvent(self, event):
        if event.key() == 32:
            for index in self.selectedRows():
                self.model().toggleRow(index)
        if event.key() == Qt.Key_D and event.modifiers() == Qt.ControlModifier:
            selfiles = []
            for index in self.selectedRows():
                selfiles.append(self.model().getRow(index)[COL_PATH])
            visdiff.visualdiff(self.repo.ui, self.repo, selfiles, {})
        else:
            return super(WctxFileTree, self).keyPressEvent(event)

    def dragObject(self):
        urls = []
        for index in self.selectedRows():
            path = self.model().getRow(index)[COL_PATH]
            u = QUrl()
            u.setPath('file://' + os.path.join(self.repo.root, path))
            urls.append(u)
        if urls:
            d = QDrag(self)
            m = QMimeData()
            m.setUrls(urls)
            d.setMimeData(m)
            d.start(Qt.CopyAction)

    def mousePressEvent(self, event):
        self.pressPos = event.pos()
        self.pressTime = QTime.currentTime()
        return QTreeView.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        d = event.pos() - self.pressPos
        if d.manhattanLength() < QApplication.startDragDistance():
            return QTreeView.mouseMoveEvent(self, event)
        elapsed = self.pressTime.msecsTo(QTime.currentTime())
        if elapsed < QApplication.startDragTime():
            return QTreeView.mouseMoveEvent(self, event)
        self.dragObject()
        return QTreeView.mouseMoveEvent(self, event)

    def customContextMenuRequested(self, point):
        selrows = []
        for index in self.selectedRows():
            path, status, mst, u, ext, sz = self.model().getRow(index)
            selrows.append((set(status+mst.lower()), path))
        point = self.mapToGlobal(point)
        action = wctxactions.wctxactions(self, point, self.repo, selrows)
        if action:
            self.emit(SIGNAL('menuAction()'))

    def selectedRows(self):
        return self.selectionModel().selectedRows()

class WctxModel(QAbstractTableModel):
    def __init__(self, wctx, ms, opts, checked, parent=None):
        QAbstractTableModel.__init__(self, parent)
        rows = []
        def mkrow(fname, st):
            ext, sizek = '', ''
            try:
                mst = fname in ms and ms[fname].upper() or ""
                name, ext = os.path.splitext(fname)
                sizebytes = wctx[fname].size()
                sizek = (sizebytes + 1023) // 1024
            except EnvironmentError:
                pass
            return [fname, st, mst, hglib.tounicode(fname), ext[1:], sizek]
        if opts['modified']:
            for m in wctx.modified():
                checked[m] = checked.get(m, True)
                rows.append(mkrow(m, 'M'))
        if opts['added']:
            for a in wctx.added():
                checked[a] = checked.get(a, True)
                rows.append(mkrow(a, 'A'))
        if opts['removed']:
            for r in wctx.removed():
                mst = r in ms and ms[r].upper() or ""
                checked[r] = checked.get(r, True)
                rows.append(mkrow(r, 'R'))
        if opts['deleted']:
            for d in wctx.deleted():
                mst = d in ms and ms[d].upper() or ""
                checked[d] = checked.get(d, False)
                rows.append(mkrow(d, '!'))
        if opts['unknown']:
            for u in wctx.unknown():
                checked[u] = checked.get(u, False)
                rows.append(mkrow(u, '?'))
        if opts['ignored']:
            for i in wctx.ignored():
                checked[i] = checked.get(i, False)
                rows.append(mkrow(i, 'I'))
        if opts['clean']:
            for c in wctx.clean():
                checked[c] = checked.get(c, False)
                rows.append(mkrow(c, 'C'))
        if opts['subrepo']:
            for s in wctx.dirtySubrepos:
                checked[s] = checked.get(s, False)
                rows.append(mkrow(s, 'S'))
        self.headers = ('*', _('Stat'), _('M'), _('Filename'), 
                        _('Type'), _('Size (KB)'))
        self.checked = checked
        self.unfiltered = rows
        self.rows = rows

    def rowCount(self, parent):
        if parent.isValid():
            return 0 # no child
        return len(self.rows)

    def columnCount(self, parent):
        if parent.isValid():
            return 0 # no child
        return len(self.headers)

    def data(self, index, role):
        if not index.isValid():
            return QVariant()

        path, status, mst, upath, ext, sz = self.rows[index.row()]
        if index.column() == COL_PATH:
            if role == Qt.CheckStateRole:
                # also Qt.PartiallyChecked
                if self.checked[path]:
                    return Qt.Checked
                else:
                    return Qt.Unchecked
        elif role == Qt.DisplayRole:
            return QVariant(self.rows[index.row()][index.column()])
        elif role == Qt.TextColorRole:
            if mst:
                return _colors.get(mst.lower(), QColor('black'))
            else:
                return _colors.get(status, QColor('black'))
        elif role == Qt.ToolTipRole:
            return QVariant(statusMessage(status, mst, upath))
        '''
        elif role == Qt.DecorationRole and index.column() == COL_STATUS:
            if status in statusTypes:
                ico = QIcon()
                ico.addPixmap(QPixmap('icons/' + statusTypes[status].icon))
                return QVariant(ico)
        '''
        return QVariant()

    def headerData(self, col, orientation, role):
        if role != Qt.DisplayRole or orientation != Qt.Horizontal:
            return QVariant()
        else:
            return QVariant(self.headers[col])

    def flags(self, index):
        flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled
        if index.column() == COL_PATH:
            flags |= Qt.ItemIsUserCheckable
        return flags

    # Custom methods

    def anyMerge(self):
        for r in self.rows:
            if r[COL_MERGE_STATE]:
                return True
        return False

    def getRow(self, index):
        assert index.isValid()
        return self.rows[index.row()]

    def getAllRows(self):
        for row in self.rows:
            yield row

    def toggleRow(self, index):
        'Connected to "activated" signal, emitted by dbl-click or enter'
        if QApplication.keyboardModifiers() & Qt.ControlModifier:
            # ignore Ctrl-Enter events, the user does not want a row
            # toggled just as they are committing.
            return
        assert index.isValid()
        fname = self.rows[index.row()][COL_PATH]
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.checked[fname] = not self.checked[fname]
        self.emit(SIGNAL("layoutChanged()"))
        self.emit(SIGNAL("checkToggled()"))

    def pressedRow(self, index):
        'Connected to "pressed" signal, emitted by mouse clicks'
        assert index.isValid()
        if index.column() == COL_PATH:
            self.toggleRow(index)

    def sort(self, col, order):
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        if col == COL_PATH:
            c = self.checked
            self.rows.sort(lambda x, y: cmp(c[x[col]], c[y[col]]))
        else:
            self.rows.sort(lambda x, y: cmp(x[col], y[col]))
        if order == Qt.DescendingOrder:
            self.rows.reverse()
        self.emit(SIGNAL("layoutChanged()"))
        self.reset()

    def setFilter(self, match):
        'simple match in filename filter'
        self.emit(SIGNAL("layoutAboutToBeChanged()"))
        self.rows = [r for r in self.unfiltered if match in r[COL_PATH_DISPLAY]]
        self.emit(SIGNAL("layoutChanged()"))
        self.reset()

    def getChecked(self):
        return self.checked.copy()

def statusMessage(status, mst, upath):
    tip = ''
    if status in statusTypes:
        upath = "<span style='font-family:Courier'>%s </span>" % upath
        tip = statusTypes[status].desc % upath
        if mst == 'R':
            tip += _(', resolved merge')
        elif mst == 'U':
            tip += _(', unresolved merge')
    return tip

class StatusType(object):
    preferredOrder = 'MAR?!ICS'
    def __init__(self, name, icon, desc, uilabel):
        self.name = name
        self.icon = icon
        self.desc = desc
        self.uilabel = uilabel

statusTypes = {
    'M' : StatusType('modified', 'menucommit.ico', _('%s is modified'),
                     'status.modified'),
    'A' : StatusType('added', 'fileadd.ico', _('%s is added'),
                     'status.added'),
    'R' : StatusType('removed', 'filedelete.ico', _('%s is removed'),
                     'status.removed'),
    '?' : StatusType('unknown', 'shelve.ico', _('%s is not tracked (unknown)'),
                     'status.unknown'),
    '!' : StatusType('deleted', 'menudelete.ico', _('%s is missing!'),
                     'status.deleted'),
    'I' : StatusType('ignored', 'ignore.ico', _('%s is ignored'),
                     'status.ignored'),
    'C' : StatusType('clean', '', _('%s is not modified (clean)'),
                     'status.clean'),
    'S' : StatusType('subrepo', 'hg.ico', _('%s is a dirty subrepo'),
                     'status.subrepo'),
}

class StatusDialog(QDialog):
    'Standalone status browser'
    def __init__(self, pats, opts, parent=None):
        QDialog.__init__(self, parent)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 6, 0, 0)
        self.setLayout(layout)
        self.stwidget = StatusWidget(pats, opts, None, self)
        layout.addWidget(self.stwidget, 1)

        self.stbar = QStatusBar(self)
        layout.addWidget(self.stbar)

        s = QSettings()
        self.stwidget.restoreState(s.value('status/state').toByteArray())
        self.restoreGeometry(s.value('status/geom').toByteArray())

        self.connect(self.stwidget, SIGNAL('titleTextChanged'), self.setTitle)
        self.connect(self.stwidget, SIGNAL('errorMessage'), self.errorMessage)
        self.setTitle(self.stwidget.getTitle())

    def setTitle(self, title):
        self.setWindowTitle(title)

    def errorMessage(self, msg):
        self.stbar.showMessage(msg)

    def accept(self):
        s = QSettings()
        s.setValue('status/state', self.stwidget.saveState())
        s.setValue('status/geom', self.saveGeometry())
        QDialog.accept(self)

    def reject(self):
        s = QSettings()
        s.setValue('status/state', self.stwidget.saveState())
        s.setValue('status/geom', self.saveGeometry())
        QDialog.reject(self)

def run(ui, *pats, **opts):
    return StatusDialog(pats, opts)
